﻿using System.ComponentModel;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Input;
using GeoClipmapping;

namespace GeoClipmapping.Components
{
    public class FreeCameraComponent : GameComponent
    {
        #region Variables
        private Camera camera;

        private float yaw;
        private float pitch;

        private float rotatespeed = 10;
        private float movespeed = 10;

        MouseState newMouse;
        MouseState oldMouse;

        KeyboardState newKeys;
        KeyboardState oldKeys;

        GamePadState newPad;
        GamePadState oldPad;
        #endregion

        #region Properties
        /// <summary>
        /// Gets the used camera.
        /// </summary>
        public Camera Camera
        {
            get { return camera; }
        }

        /// <summary>
        /// Gets or sets the movement speed.
        /// </summary>
        public float MovementSpeed
        {
            get { return movespeed; }
            set { movespeed = value; }
        }

        /// <summary>
        /// Gets or sets the rotate speed.
        /// </summary>
        public float RotateSpeed
        {
            get { return rotatespeed; }
            set { rotatespeed = value; }
        }
        #endregion

        #region Constructor
        /// <summary>
        /// Creates a simple camera that is controllable with the right mouse button and
        /// W S A D Q E keys.
        /// </summary>
        public FreeCameraComponent(Game game)
            : base(game)
        {
            camera = new Camera();

            yaw = MathHelper.ToRadians(0);
            pitch = MathHelper.ToRadians(0);

            newMouse = new MouseState();
            oldMouse = newMouse;

            newKeys = new KeyboardState();
            oldKeys = newKeys;
        }
        #endregion

        #region Update

        /// <summary>
        /// Updates the camera. Handles input and moves camera.
        /// </summary>
        /// <param name="gameTime"></param>
        public override void Update(GameTime gameTime)
        {
            oldMouse = newMouse;
            newMouse = Mouse.GetState();

            oldKeys = newKeys;
            newKeys = Keyboard.GetState();

            oldPad = newPad;
            newPad = GamePad.GetState(PlayerIndex.One);

            float speed = movespeed * (float)gameTime.ElapsedGameTime.TotalSeconds;
            float rotate = rotatespeed * (float)gameTime.ElapsedGameTime.TotalSeconds;

            Vector3 left = Vector3.Normalize(Vector3.Cross(camera.UpVector, camera.Forward));

            // Speedup when shift is pressed.
            if (newKeys.IsKeyDown(Keys.LeftShift))
            {
                speed *= 3;
            }
            else if (newPad.IsButtonDown(Buttons.LeftTrigger))
            {
                speed *= newPad.Triggers.Left * 3;
            }

            // Move forward
            if (newKeys.IsKeyDown(Keys.W) ||
                newPad.IsButtonDown(Buttons.LeftThumbstickUp))
            {
                camera.Position += camera.Forward * speed;
                camera.Target += camera.Forward * speed;
            }

            // Move backward
            if (newKeys.IsKeyDown(Keys.S) ||
                newPad.IsButtonDown(Buttons.LeftThumbstickDown))
            {
                camera.Position -= camera.Forward * speed;
                camera.Target -= camera.Forward * speed;
            }

            // Move left
            if (newKeys.IsKeyDown(Keys.A) ||
                newPad.IsButtonDown(Buttons.LeftThumbstickLeft) ||
                newKeys.IsKeyDown(Keys.Left) ||
                newPad.IsButtonDown(Buttons.DPadLeft))
            {
                camera.Position += left * speed;
                camera.Target += left * speed;
            }

            // Move right
            if (newKeys.IsKeyDown(Keys.D) ||
                newPad.IsButtonDown(Buttons.LeftThumbstickRight) ||
                newKeys.IsKeyDown(Keys.Right) ||
                newPad.IsButtonDown(Buttons.DPadRight))
            {
                camera.Position -= left * speed;
                camera.Target -= left * speed;
            }

            // Move up
            if (newKeys.IsKeyDown(Keys.E) || newPad.IsButtonDown(Buttons.RightShoulder))
            {
                camera.Position += camera.UpVector * speed;
                camera.Target += camera.UpVector * speed;
            }

            // Move down
            if (newKeys.IsKeyDown(Keys.Q) || newPad.IsButtonDown(Buttons.RightTrigger))
            {
                camera.Position -= camera.UpVector * speed;
                camera.Target -= camera.UpVector * speed;
            }


            // Move forward on XZ plane
            if (newKeys.IsKeyDown(Keys.Up) || newPad.IsButtonDown(Buttons.DPadUp))
            {
                Vector3 forward = camera.Forward;
                forward.Y = 0;
                forward.Normalize();

                camera.Position += forward * speed;
                camera.Target += forward * speed;
            }

            // Move backward on XZ plane
            if (newKeys.IsKeyDown(Keys.Down) || newPad.IsButtonDown(Buttons.DPadDown))
            {
                Vector3 forward = camera.Forward;
                forward.Y = 0;
                forward.Normalize();

                camera.Position -= forward * speed;
                camera.Target -= forward * speed;
            }


            // Yaw / Pitch
            Vector2 mouseMove = Vector2.Zero;
            if (newMouse.RightButton == ButtonState.Pressed)
            {
                mouseMove = new Vector2(oldMouse.X - newMouse.X, oldMouse.Y - newMouse.Y);
                mouseMove *= rotate;
            }

            if (oldPad.ThumbSticks.Right != Vector2.Zero)
            {
                mouseMove = -oldPad.ThumbSticks.Right;
                mouseMove *= rotate;
            }

            // Yaw
            if (mouseMove.X != 0)
            {
                yaw += mouseMove.X;
            }

            // Pitch
            if (mouseMove.Y != 0)
            {
                pitch += mouseMove.Y;
                pitch = MathHelper.Clamp(pitch, -90, 90);
            }

            camera.SetRotationAroudPosition(MathHelper.ToRadians(yaw), MathHelper.ToRadians(pitch), 0);
            camera.Refresh();
            base.Update(gameTime);
        }
        #endregion
    }
}
